package cn.org.xuanguang.kepler.hormos.rdb.executor;

import cn.org.xuanguang.kepler.hormos.rdb.dml.warrper.DefaultColumnWrapperContext;
import cn.org.xuanguang.kepler.hormos.rdb.dml.warrper.ResultWrapper;
import cn.org.xuanguang.kepler.hormos.rdb.dsl.sql.BatchSqlRequest;
import cn.org.xuanguang.kepler.hormos.rdb.dsl.sql.SqlRequest;
import cn.org.xuanguang.kepler.metadata.QueryResult;
import cn.org.xuanguang.kepler.metadata.enums.DataSourceType;
import cn.org.xuanguang.kepler.util.jdbc.JdbcUtils;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.sql.*;
import java.util.Date;
import java.util.List;

import static cn.org.xuanguang.kepler.hormos.rdb.util.SqlUtils.printSql;


/**
 * jdbc版本的同步执行器
 */
@Data
@Slf4j
public class JdbcSyncSqlExecutor implements SyncSqlExecutor {

    private DataSource dataSource;
    private DataSourceType dataSourceType;


    private volatile PreparedStatement statement;

    protected String getDatasourceId() {
        return "default";
    }

    public JdbcSyncSqlExecutor(DataSource dataSource, DataSourceType dataSourceType) {
        this.dataSource = dataSource;
        this.dataSourceType = dataSourceType;
    }


    public Connection getConnection(SqlRequest sqlRequest) {
        Connection connection = DataSourceUtils.getConnection(dataSource);
        boolean isConnectionTransactional = DataSourceUtils.isConnectionTransactional(connection, dataSource);
        if (log.isDebugEnabled()) {
            log.debug("DataSource ({}) JDBC Connection [{}] will {}be managed by Spring", getDatasourceId(), connection, (isConnectionTransactional ? "" : "not "));
        }
        return connection;
    }

    public void releaseConnection(Connection connection, SqlRequest sqlRequest) {
        if (log.isDebugEnabled()) {
            log.debug("Releasing DataSource ({}) JDBC Connection [{}]", getDatasourceId(), connection);
        }
        try {
            DataSourceUtils.doReleaseConnection(connection, dataSource);
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
            try {
                connection.close();
            } catch (Exception e2) {
                log.error(e2.getMessage(), e2);
            }
        }
    }

    @SneakyThrows
    public void releaseStatement(Statement statement) {
        statement.close();
    }

    @SneakyThrows
    public void releaseResultSet(ResultSet resultSet) {
        resultSet.close();
    }


    /**
     * 更新操作
     *
     * @param request sql请求
     * @return
     */

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int update(SqlRequest request) {
        Connection connection = getConnection(request);
        try {
            return doUpdate(connection, request);
        } finally {
            releaseConnection(connection, request);
        }
    }

    @SneakyThrows
    protected int doUpdate(Connection connection, SqlRequest request) {
        printSql(log, request);
        PreparedStatement statement = null;
        try {
            int count = 0;
            if (!request.isEmpty()) {
                statement = connection.prepareStatement(request.getSql());
                preparedStatementParameter(statement, request.getParameters());
                count += statement.executeUpdate();
                log.debug("==>    Updated: {}", count);
            }
            // 批量添加
            if (request instanceof BatchSqlRequest) {
                for (SqlRequest batch : ((BatchSqlRequest) request).getBatch()) {
                    if (!batch.isEmpty()) {
                        if (null != statement) {
                            releaseStatement(statement);
                        }
                        printSql(log, batch);
                        statement = connection.prepareStatement(batch.getSql());
                        preparedStatementParameter(statement, batch.getParameters());
                        int rows = statement.executeUpdate();
                        count += rows;
                        log.debug("==>    Updated: {}", rows);
                    }
                }
            }
            return count;
        } finally {
            if (null != statement) {
                releaseStatement(statement);
            }
        }
    }

    /**
     * 查询操作
     *
     * @param request sql请求
     * @param wrapper 查询结果集
     * @return
     */
    @Override
    @Transactional(readOnly = true)
    public <T, R> R select(SqlRequest request, ResultWrapper<T, R> wrapper) {
        Connection connection = getConnection(request);
        try {
            return doSelect(connection, request, wrapper);
        } finally {
            releaseConnection(connection, request);
        }

    }

    @SneakyThrows
    protected <T, R> R doSelect(Connection connection, SqlRequest request, ResultWrapper<T, R> wrapper) {
        PreparedStatement statement = connection.prepareStatement(request.getSql());
        try {
            printSql(log, request);
            preparedStatementParameter(statement, request.getParameters());
            ResultSet resultSet = statement.executeQuery();
            QueryResult queryResult = JdbcUtils.handleResultSet(resultSet, 10000);
            List<String> columns = queryResult.getColumnNames();
            List<String> columnsType = queryResult.getColumnTypes();
            List<String> columnClassNames = queryResult.getColumnClassNames();

            // 包装结果
            queryResult.getRows().forEach(row -> {
                //调用包装器,将查询结果包装为对象
                T data = wrapper.newRowInstance();
                for (int i = 0; i < columns.size(); i++) {
                    String column = columns.get(i);
                    // 列类型
                    String type = columnsType.get(i);
                    String columnClassName = columnClassNames.get(i);
                    Object value = getResultValue(type, row.get(i));
//                  把行数据封装成对象
                    DefaultColumnWrapperContext<T> context = new DefaultColumnWrapperContext<>(i, column, value, data);
                    //直接转换
                    wrapper.wrapColumn(context);
                }
                if (!wrapper.completedWrapRow(data)) {
                    log.debug("wrapper completedWrapRow ");
                }
            });
            wrapper.completedWrap();
            log.debug("==>    Results: {}", queryResult.getRows().size());
            releaseResultSet(resultSet);
            return wrapper.getResult();
        } finally {
            releaseStatement(statement);
        }
    }

    /**
     * 给预处理语句设置参数。
     *
     * @param statement 预处理语句对象，用于设置参数。
     * @param parameter 参数数组，可以包含null值或者特殊的NullValue对象。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    protected static void preparedStatementParameter(PreparedStatement statement, Object[] parameter) throws SQLException {
        // 如果参数数组为空或长度为0，则直接返回，不执行后续操作
        if (parameter == null || parameter.length == 0) {
            return;
        }
        int index = 1;
        // 遍历参数数组，为预处理语句设置参数
        for (Object object : parameter) {
            // 对于null值，使用setNull方法设置为NULL类型
            if (object == null) {
                statement.setNull(index++, Types.NULL);
                // 注释掉的代码块是为特殊NullValue对象准备的，目前不启用
                // } else if (object instanceof NullValue) {
                //     statement.setNull(index++, ((NullValue) object).getDataType().getSqlType().getVendorTypeNumber());
            } else if (object instanceof Date) {
                // 对于Date类型，设置为Timestamp类型
                statement.setTimestamp(index++, new Timestamp(((Date) object).getTime()));
            } else if (object instanceof byte[]) {
                // 对于字节数组，设置为BLOB类型
                statement.setBlob(index++, new ByteArrayInputStream((byte[]) object));
            } else {
                // 对于其他类型，使用setObject方法设置
                statement.setObject(index++, object);
            }
        }
    }

    /**
     * 预处理sql的值，用来解决sql对象转java对象问题
     *
     * @param columnsType
     * @param value
     * @return
     */
    @SneakyThrows
    protected Object getResultValue(String columnsType, Object value) {
        if (value == null) {
            return value;
        }
        switch (columnsType) {
            case "TIMESTAMP":
                return new Date(String.valueOf(value));
//            case Types.TIME:
//                return  LocalTime.parse((CharSequence) value);
//            case Types.LONGVARCHAR:
//            case Types.VARCHAR:
//                return value;
//            case Types.DATE:
//                return Instant.parse((CharSequence) value);
//            case Types.CLOB:
//                return  value;
//            case Types.BLOB:
//                return  value;
            default:
                return value;
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @SneakyThrows
    public void execute(SqlRequest request) {
        Connection connection = getConnection(request);
        try {
            doExecute(connection, request);
        } finally {
            releaseConnection(connection, request);
        }
    }

    @SneakyThrows
    protected void doExecute(Connection connection, SqlRequest request) {
        PreparedStatement statement = null;
        try {
            if (!request.isEmpty()) {
                printSql(log, request);
                statement = connection.prepareStatement(request.getSql());
                preparedStatementParameter(statement, request.getParameters());
                statement.execute();
            }

            if (request instanceof BatchSqlRequest) {
                for (SqlRequest batch : ((BatchSqlRequest) request).getBatch()) {
                    if (!batch.isEmpty()) {
                        if (null != statement) {
                            releaseStatement(statement);
                        }
                        printSql(log, batch);
                        statement = connection.prepareStatement(batch.getSql());
                        preparedStatementParameter(statement, batch.getParameters());
                        statement.execute();
                    }
                }
            }
        } finally {
            if (null != statement) {
                releaseStatement(statement);
            }
        }
    }
}
